﻿#region License
/* ---------------------------------------------------------------- *
 *
 * This file is part of the Xcoordination Application Space
 * ("XcoAppSpace") http://www.xcoordination.com
 *
 * Copyright (C) 2009 Xcoordination http://www.xcoordination.com
 *
 * XcoAppSpace is free software; you can redistribute it and/or
 * modify it under the terms of version 2 of the GNU General
 * Public License as published by the Free Software Foundation.
 *
 * XcoAppSpace is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see http://www.gnu.org/licenses/
 * or write to the Free Software Foundation, Inc., 51 Franklin
 * Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 * ---------------------------------------------------------------- */
#endregion

using System;
using System.Reflection;
using System.Collections.Generic;
using System.Linq;
using System.IO;
using System.Text;
//using XcoAppSpaces.Contracts.Logging;

#if SILVERLIGHT
using System.Windows;
using System.Windows.Resources;
#endif

namespace TypeResolve
{
	/// <summary>
	/// Contains methods to load assemblies and search for types for:
	/// - .Net Framework
	/// - .Net Compact Framework
	/// - Silverlight
	/// </summary>
	public static class TypeUtils
	{
		//private static IXcoLogger log = XcoLoggerFactory.CreateInstance(typeof (TypeUtils));
		private static List<Assembly> currentAssemblies;
		private static bool assembliesLoaded = false;

		static TypeUtils()
		{
			Initialize();
		}

		public static void Initialize()
		{
			assembliesLoaded = false;
#if !PocketPC && !SILVERLIGHT
			currentAssemblies = AppDomain.CurrentDomain.GetAssemblies().ToList();
			AppDomain.CurrentDomain.AssemblyResolve += AssemblyResolveEventHandler;
		}

		private static Assembly AssemblyResolveEventHandler(object sender, ResolveEventArgs args)
		{
			//this is e.g. called by the BinaryFormatter when an assembly should be resolved
			var shortName = GetShortAssemblyName(args.Name);
			var assembly = currentAssemblies.FirstOrDefault(a => GetShortAssemblyName(a.FullName) == shortName);
			if (assembly == null && !assembliesLoaded)
			{
				currentAssemblies = LoadCurrentAssemblies().ToList();
				assembly = currentAssemblies.FirstOrDefault(a => GetShortAssemblyName(a.FullName) == shortName);
			}
			//if (assembly == null)
			//    log.Debug("AssemblyResolve: Assembly " + shortName + " not found!");
			//else
			//    log.Debug("AssemblyResolve: Resolved " + shortName);
			return assembly;
		}

		private static string GetShortAssemblyName(string fullName)
		{
			var idx = fullName.IndexOf(',');
			if (idx == -1)
				return fullName;
			return fullName.Substring(0, idx);
		}

		public static void LoadAdditionalAssemblies(string dir)
		{
			//allows loading additional assemblies (can be useful when not all assemblies of the application are placed in the same directory)
			//log.Debug("Loading assitional Assemblies from path: " + dir);
			var assemblies = currentAssemblies.ToList();
			LoadAssembliesFromPath(dir, assemblies);
			currentAssemblies = assemblies;
		}

		public static void LoadAdditionalAssemblies(Assembly assemblyInSamePath)
		{
			var dir = Path.GetDirectoryName(assemblyInSamePath.Location);
			LoadAdditionalAssemblies(dir);
		}
#else
			currentAssemblies = LoadCurrentAssemblies().ToList();
		}
#endif


		public static IEnumerable<Assembly> CurrentAssemblies
		{ 
			get { return currentAssemblies; }
		}

		public static Type SearchType(string typeName)
		{
			//log.Debug("Resolving type: " + typeName);
			var result =
				Type.GetType(typeName) ??
				SearchTypeInAssemblies(typeName, currentAssemblies, "current assemblies"); //??
				//SearchTypeInAssemblies(typeName, AppDomain.CurrentDomain.GetAssemblies(), "domain assemblies"); //search again in appdomain assemblies because additional assemblies may have been loaded
			//if type not found and not all assemblies have been loaded yet, try again by loading all assemblies first
			if (result == null && !assembliesLoaded)
			{
				currentAssemblies = LoadCurrentAssemblies().ToList();
				return SearchType(typeName);
			}
			//if (result == null)
			//	log.Debug("Type " + typeName + " was not found!");
			return result;
		}

		private static Type SearchTypeInAssemblies(string typeName, IEnumerable<Assembly> assemblies, string listName)
		{
			//log.Debug("Type lookup in list: " + listName);
			Type result = null;
			foreach (var asm in assemblies)
			{
				result = asm.GetType(typeName);
				if (result != null)
					break;
			}
			return result;
		}

#if PocketPC || !SILVERLIGHT
		public static IEnumerable<Assembly> LoadCurrentAssemblies()
		{
			if (assembliesLoaded)
				return currentAssemblies;
			
			//Must search through whole directory because AppDomain.CurrentDomain.GetAssemblies() only returns assemblies that have already been loaded
			var assemblies = (currentAssemblies != null ? currentAssemblies.ToList() : new List<Assembly>());
	#if !PocketPC
			string dir = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
			//in asp.net projects, the assembly path is a temporary path somewhere under ...\temporary asp.net files\..., therefore another method for getting the directory must be used:
			if (dir == null || dir.IndexOf("asp.net", StringComparison.OrdinalIgnoreCase) > 0)
				dir = AppDomain.CurrentDomain.BaseDirectory;
	#else
			string dir = Path.GetDirectoryName(Assembly.GetExecutingAssembly().GetName().CodeBase);
	#endif
			LoadAssembliesFromPath(dir, assemblies);
			assembliesLoaded = true;
			return assemblies;
		}

		private static void LoadAssembliesFromPath(string dir, List<Assembly> assemblies)
		{
			foreach (var file in Directory.GetFiles(dir, "*.dll").Concat(Directory.GetFiles(dir, "*.exe")))
			{
				try
				{
					var asm = Assembly.LoadFrom(file);
					if (!assemblies.Contains(asm))
					{
						assemblies.Add(asm);
						//log.Debug("Loaded Assembly: " + asm.FullName);
					}
				}
				catch (Exception) // ex)
				{
					//Console.WriteLine("Error loading assembly: " + ex.Message);
				}
			}
		}

#endif

#if SILVERLIGHT
		public static IEnumerable<Assembly> LoadCurrentAssemblies()
		{
			if (assembliesLoaded)
				return currentAssemblies;
			
			var assemblies = new List<Assembly>();
			UIInvokeSync(() =>
			             	{
			             		//Deployment.Current.Parts can only be accessed from the UI thread??
			             		foreach (var part in System.Windows.Deployment.Current.Parts)
			             		{
			             			StreamResourceInfo info =Application.GetResourceStream(new Uri(part.Source, UriKind.Relative));
			             			Assembly assembly = new AssemblyPart().Load(info.Stream);
			             			assemblies.Add(assembly);
			             		}
			             	});

			assembliesLoaded = true;
			return assemblies;
		}

		private static void UIInvokeSync(Action a)
		{
			if (Deployment.Current.Dispatcher.CheckAccess())
			{
				a();  // meaning this is already the UI thread so just do it;
			}
			else
			{
				var e = new System.Threading.ManualResetEvent(false);
				Deployment.Current.Dispatcher.BeginInvoke(() => { a(); e.Set(); });
				e.WaitOne();
			}
		}
#endif

#if PocketPC
		//CF has no methods to make array types - the following extension methods are used as replacement
		public static Type MakeArrayType(this Type type, int arrayRank)
		{
			var str = new StringBuilder(type.FullName);
			str.Append("[");
			for (int i = 1; i < arrayRank; i++)
				str.Append(",");
			str.Append("]");
			return TypeUtils.SearchType(str.ToString());
		}

		public static Type MakeArrayType(this Type type)
		{
			return TypeUtils.SearchType(type.FullName + "[]");
		}
#endif
	}
}
